/**********************************************************************
*   Copyright: (C)2023 LingYun IoT System Studio
*      Author: GuoWenxue<guowenxue@gmail.com> QQ: 281143292
* Description: ISKBoard GPIO simulate I2C Bus Hardware Abstract Layer driver
*
*   ChangeLog:
*        Version    Date       Author            Description
*        V1.0.0  2023.04.3    GuoWenxue      Release initial version
***********************************************************************/

#include "i2c_bitbang.h"

i2c_bus_t i2c_bus[I2CBUSMAX] =
{
    /* Bus   ChipAddr       SCL_Pin              SDA_Pin      */
    {I2CBUS0, 0x00, {GPIOB, GPIO_PIN_6 }, {GPIOB, GPIO_PIN_7 } }, /* SHT20, RTC, OLED */
	{I2CBUS1, 0x00, {GPIOB, GPIO_PIN_10}, {GPIOB, GPIO_PIN_11} }, /* MikroBUS I2C interface */
};


enum
{
	LOW,
	HIGH,
};


/*+--------------------------+
 *|    Low level GPIO API    |
 *+--------------------------+*/

static inline void SDA_IN(uint8_t bus)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	GPIO_InitStruct.Pin = i2c_bus[bus].sda.pin;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(i2c_bus[bus].sda.group, &GPIO_InitStruct);
}

static inline void SDA_OUT(uint8_t bus)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	GPIO_InitStruct.Pin = i2c_bus[bus].sda.pin;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(i2c_bus[bus].sda.group, &GPIO_InitStruct);
}

static inline void SCL_OUT(uint8_t bus)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	GPIO_InitStruct.Pin = i2c_bus[bus].scl.pin;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(i2c_bus[bus].scl.group, &GPIO_InitStruct);
}

static inline void SCL_SET(uint8_t bus, int level)
{
	HAL_GPIO_WritePin(i2c_bus[bus].scl.group, i2c_bus[bus].scl.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
}

static inline void SDA_SET(uint8_t bus, int level)
{
	HAL_GPIO_WritePin(i2c_bus[bus].sda.group, i2c_bus[bus].sda.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
}

static inline GPIO_PinState SCL_GET(uint8_t bus)
{
	return HAL_GPIO_ReadPin(i2c_bus[bus].scl.group, i2c_bus[bus].scl.pin);
}

static inline GPIO_PinState SDA_GET(uint8_t bus)
{
	return HAL_GPIO_ReadPin(i2c_bus[bus].sda.group, i2c_bus[bus].sda.pin);
}


/*+--------------------------+
 *|  I2C Clock Sequence API  |
 *+--------------------------+*/

/* */
#if 0
#include "miscdev.h"  /* delay_us() implement by TIM6 */
#else
static inline void delay_us(uint32_t us)
{
	uint32_t      i, j;
	for(j=0; j<450; j++)// 450 ~500
		for(i = 0; i < us; i++)
		{
			__NOP();
		}
}
#endif


void I2C_StartCondition(uint8_t bus)
{
	SDA_OUT(bus);
	SCL_OUT(bus);

	/* Start Condition Clock Sequence:
	          ______
     SCL:           |___
            _____
     SDA:        |_____
	*/
	SDA_SET(bus, HIGH);
	SCL_SET(bus, HIGH);
	delay_us(1);

	SDA_SET(bus, LOW);
	delay_us(1);  // hold time start condition (t_HD;STA)
	SCL_SET(bus, LOW);
	delay_us(1);
}

void I2C_StopCondition(uint8_t bus)
{
	SDA_OUT(bus);

	/* SCL Stop Condition Clock Sequence:
				_______
      SCL:   ___|
                   _____
      SDA:   _____|
	*/
	SCL_SET(bus, LOW);
	SDA_SET(bus, LOW);
	delay_us(1);

	SCL_SET(bus, HIGH);
	SDA_SET(bus, HIGH);
	delay_us(1);
}

void I2C_Ack(uint8_t bus, uint8_t ack)
{
	if(ACK_NONE == ack)
		return ;

	SCL_SET(bus, LOW);
	SDA_OUT(bus);

	if( ACK==ack )
		SDA_SET(bus, LOW);
	else if( NAK==ack )
		SDA_SET(bus, HIGH);

	delay_us(1);

	SCL_SET(bus, HIGH);
	delay_us(1);
	SCL_SET(bus, LOW);
}

uint8_t I2C_WriteByte(uint8_t bus, uint8_t txByte)
{
	uint8_t error = NO_ERROR;
	uint8_t mask;

	SDA_OUT(bus);
	SCL_SET(bus, LOW);

	/* start send 8 bits data */
	for(mask=0x80; mask>0; mask>>=1)
	{
		if((mask & txByte) == 0)
			SDA_SET(bus, LOW);
		else
			SDA_SET(bus, HIGH);

		delay_us(1);	// data set-up time (t_SU;DAT)
		SCL_SET(bus, HIGH);
		delay_us(1);	// SCL high time (t_HIGH)
		SCL_SET(bus, LOW);
		delay_us(1);	// data hold time(t_HD;DAT)
	}

	/* Wait for ACK/NAK */
	SDA_IN(bus);
	SCL_SET(bus, HIGH); // clk #9 for ack
	delay_us(1); // data set-up time (t_SU;DAT)

	/* High level means NAK */
	if( SDA_GET(bus) )
		error = ACK_ERROR;

	SCL_SET(bus, LOW);

	delay_us(1);

	return error;
}

static inline uint8_t I2C_WaitWhileClockStreching(uint8_t bus, uint8_t timeout)
{
	uint8_t error = NO_ERROR;

	while( SCL_GET(bus)== 0)
	{
		if(timeout-- == 0)
			return TIMEOUT_ERROR;

		delay_us(10);
	}
	return error;
}

uint8_t I2C_ReadByte(uint8_t bus, uint8_t *rxByte, uint8_t ack, uint32_t timeout)
{
//==============================================================================
	uint8_t error = NO_ERROR;
	uint8_t mask;

	*rxByte = 0x00;
	SDA_IN(bus);

	/* start read 8 bits data */
	for(mask = 0x80; mask > 0; mask >>= 1)
	{
		SCL_SET(bus, HIGH);  // start clock on SCL-line
		delay_us(1);         // clock set-up time (t_SU;CLK)

		// wait while clock streching
		error = I2C_WaitWhileClockStreching(bus, timeout);

		delay_us(1); // SCL high time (t_HIGH)

		if( SDA_GET(bus) )
			*rxByte |= mask; // read bit

		SCL_SET(bus, LOW);
		delay_us(1); // data hold time(t_HD;DAT)
	}

	I2C_Ack(bus, ack);
  return error;
}


uint8_t I2C_SendAddress(uint8_t bus, uint8_t dir)
{
	uint8_t       addr;
	uint8_t       rv;

	if( I2C_WR == dir )
	{
		addr = W_ADDR(i2c_bus[bus].addr);
	}
	else
	{
		addr = R_ADDR(i2c_bus[bus].addr);
	}

	rv=I2C_WriteByte(bus, addr);

	return rv;
}

int i2c_init(uint8_t bus, uint8_t addr)
{
	if( bus >= I2CBUSMAX )
	{
		return -1;
	}

#if 0
	/* I2C bus used already */
	if( 0x00 != i2c_bus[bus].addr )
	{
		return -2;
	}
#endif

	/* Set I2C slave address */
	i2c_bus[bus].addr = addr;

	return 0;
}

int i2c_term(uint8_t bus)
{
	if( bus >= I2CBUSMAX )
	{
		return -1;
	}

	/* Set I2C slave address */
	i2c_bus[bus].addr = 0x0;

	return 0;
}

int i2c_read(uint8_t bus, uint8_t *buf, int size)
{
  int       i;
  int       rv = NO_ERROR;
	uint8_t   byte;

  I2C_StartCondition(bus);

  if( NO_ERROR != (rv=I2C_SendAddress(bus, I2C_RD) ) )
  {
    goto OUT;
  }

  for (i=0; i<size; i++)
  {
     if( !I2C_ReadByte(bus, &byte, ACK, I2C_CLK_STRETCH_TIMEOUT) )
			 buf[i] = byte;
		 else
			 goto OUT;
  }

OUT:
  I2C_StopCondition(bus);
  return rv;
}

int i2c_write(uint8_t bus, uint8_t *data, int bytes)
{
  int       i;
  int       rv = NO_ERROR;

	if(!data)
		return PARM_ERROR;

  I2C_StartCondition(bus);

  if( NO_ERROR != (rv=I2C_SendAddress(bus, I2C_WR)) )
  {
    goto OUT;
  }

  for (i=0; i<bytes; i++)
  {
    if( NO_ERROR != (rv=I2C_WriteByte(bus, data[i])) )
    {
      break;
    }
  }

OUT:
  I2C_StopCondition(bus);
  return rv;
}
